package be.hogent.iii.project.objects;

import be.hogent.iii.project.client.netwerk.Connectie;
import be.hogent.iii.project.common.bericht.GameEindeBericht;
import be.hogent.iii.project.common.bericht.GameVolgendeBeurtBericht;
import be.hogent.iii.project.common.game.objecten.Spel;
import be.hogent.iii.project.gui.SpelFrame;
import be.hogent.iii.project.objects.gebouwen.Gebouw;
import be.hogent.iii.project.objects.vakken.Vak;
import be.hogent.iii.project.objects.ventjes.Ventje;
import com.thoughtworks.xstream.XStream;
import java.io.Serializable;
import java.sql.Date;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;


/**
 *
 * @author jef donders
 */
public class Veld implements Serializable {

    private static Coordinaat[] mogelijkeVerplaatsingen = {
        new Coordinaat(-1, 0), new Coordinaat(0, 1), new Coordinaat(1, 0),
        new Coordinaat(0, -1), new Coordinaat(-1, 1), new Coordinaat(1, 1),
        new Coordinaat(1, -1), new Coordinaat(-1, -1)
    };
    private static int aantalMogelijkeVerplaatsingen = 8;
    private Vak[][] speelveld;
    private String mapnaam;
    private int grootteX;
    private int grootteY;
    private ArrayList<Entiteit> entiteiten;
    private int spelerAanBeurt;

    public Veld() {

    }

    public Veld(int grootteX, int grootteY) {
        this.grootteX = grootteX;
        this.grootteY = grootteY;
        speelveld = new Vak[grootteX][grootteY];
        entiteiten = new ArrayList<Entiteit>();
    }

    public ArrayList<Entiteit> getEntiteiten() {
        return entiteiten;
    }

    public void addEntiteit(Entiteit ob) {
        entiteiten.add(ob);
    }

    public int getGrootteX() {
        return grootteX;
    }

    public int getGrootteY() {
        return grootteY;
    }

    public void setGrootteX(int val) {
        this.grootteX = val;
    }

    public void setGrootteY(int val) {
        this.grootteY = val;
    }

    public String getMapnaam() {
        return mapnaam;
    }

    public void setMapnaam(String val) {
        this.mapnaam = val;
    }

    public Vak getVakje(int i, int j) {
        return speelveld[i][j];
    }

    public Vak getVakje(Coordinaat c) {
        return speelveld[c.getX()][c.getY()];
    }

    public void setVakje(int i, int j, Vak vakje) {
        speelveld[i][j] = vakje;
    }

    /**
     * bewegingen uitvoeren die gemaakt zijn
     * en aanvallen uitwerken
     */
    public void eindeBeurt() {
        System.out.println("\n\n\nEinde beurt verwerken: ");
        for (int i = 0; i < entiteiten.size(); i++) {
            Entiteit item = entiteiten.get(i);
            boolean[][] aanvalPlaatsen = getAanvalsPlaatsen(spelerAanBeurt);
            if (item.getClass().getPackage().getName().equalsIgnoreCase("be.hogent.iii.project.objects.ventjes")) {
                Ventje ventje = (Ventje) item;
                if (ventje.getBeweging() != null) {
                    if (aanvalPlaatsen[ventje.getBeweging().getX()][ventje.getBeweging().getY()]) {
                        System.out.println("aanval verwerken");
                        verwerkAanval(ventje);
                    } else {
                        System.out.println("ventje enkel verzetten");
                        ventje.setPlaats(ventje.getBeweging());
                        ventje.setBeweging(null);
                    }
                }
            }
        }

        Connectie conn = SpelFrame.getSpelFrame().getConnectie();

        if (testGewonnen()) {
            System.out.println("Het spel is gedaan");
            Spel spel = SpelFrame.getSpelFrame().getSpel();
            GameEindeBericht stop = new GameEindeBericht(spel);
            stop.setMapnaam(spel.getNaam());
            stop.setDatum(new Date(System.currentTimeMillis()));
            stop.setTijd(new Time(System.currentTimeMillis() - SpelFrame.getSpelFrame().getStartTijd()));
            for (int i = 0; i < spel.getDeelnemers().size(); i++) {
                if (i != spelerAanBeurt) {
                    stop.addVerliezer(spel.getDeelnemers().get(i).getNick());
                } else {
                    stop.addWinnaar(spel.getDeelnemers().get(i).getNick());
                }
            }
            XStream xStream = new XStream();
            conn.stuurBericht(xStream.toXML(stop));

        } else {
            SpelFrame frame = SpelFrame.getSpelFrame();
            List spelers = frame.getSpel().getDeelnemers();

            int aanBeurt = 0;// = spelers.indexOf(frame.getSpeler());
            for (int i = 0; i < spelers.size(); i++) {
                System.out.println("speler" + i + " : " + ((be.hogent.iii.project.common.game.objecten.Speler) spelers.get(i)).getNick() + "     " + frame.getSpeler().getNick());
                if (((be.hogent.iii.project.common.game.objecten.Speler) spelers.get(i)).getNick().equalsIgnoreCase(frame.getSpeler().getNick())) {
                    aanBeurt = i;
                }
            }

            aanBeurt = (aanBeurt + 1) % spelers.size();

            System.out.println("nummer van volgende speler: " + aanBeurt);
            System.out.println("totaal aantal spelers: " + spelers.size());

            //met XSTREAM-standaard serialisatie
            GameVolgendeBeurtBericht bericht = new GameVolgendeBeurtBericht(entiteiten, SpelFrame.getSpelFrame().getSpel());
            bericht.setNicknameAanbeurt(((be.hogent.iii.project.common.game.objecten.Speler) spelers.get(aanBeurt)).getNick());

            //met Base64-serialisatie?
            //Base64VolgendeBeurtBericht bericht = new Base64VolgendeBeurtBericht(entiteiten, ((be.hogent.iii.project.common.game.objecten.Speler)spelers.get(aanBeurt)).getNick());
            //System.out.println("Base64 bericht van de entiteiten succesvol aangemaakt, we versturen het");

            //doorsturen die handel
            XStream xStream = new XStream();
            conn.stuurBericht(xStream.toXML(bericht));
        }
    }

    /**
     * kijken of de speler die momenteel aan de beurt is het spel gewonnen heeft
     * @return
     */
    public boolean testGewonnen() {
        boolean gewonnen = true;
        int i = 0;
        while (gewonnen && i < entiteiten.size()) {
            Entiteit item = entiteiten.get(i);
            if (item.getClass().getPackage().getName().equalsIgnoreCase("be.hogent.iii.project.objects.gebouwen") && item.eigenaarId != spelerAanBeurt) {
                gewonnen = false;
            }
            i++;
        }
        return gewonnen;
    }

    /**
     * Een aanval van een ventje afwerken
     * @param ventje
     */
    public void verwerkAanval(Ventje ventje) {
        Coordinaat resultaat;
        Coordinaat naar = ventje.getBeweging();
        Coordinaat van = ventje.getPlaats();

        //kijken of er nog ventjes zijn die op dezelfde plaats staan en die dezelfde aanvalslocatie hebben
        ArrayList<Ventje> ventjes = new ArrayList<Ventje>();
        ArrayList<Entiteit> ventjesOpZelfdePlaats = getEntiteiten(van);
        for (int i = 0; i < ventjesOpZelfdePlaats.size(); i++) {
            Ventje ventje2 = (Ventje) ventjesOpZelfdePlaats.get(i);
            System.out.println("aantal: " +ventjesOpZelfdePlaats.size() + " ");
            if (ventje2.getBeweging() != null && ventje2.getBeweging().isGelijkWaardig(naar)) {
                ventjes.add(ventje2);
            }
        }
        boolean[][] geldigePlaatsen = getGeldigePlaatsen(ventjes);
        int[][] afstanden = bepaalAfstandenTabel(geldigePlaatsen, van);
        
        //het dichtliggende vakje naast de tegenstander zoeken
        Coordinaat dichtstLiggende = null;
        int dichtstePlaats = -1;
        for (int i = 0; i < aantalMogelijkeVerplaatsingen; i++) {
            if (isVerplaatsingBinnenVeld(naar, mogelijkeVerplaatsingen[i])) {
                int nieuweAfstand = afstanden[naar.getX() + mogelijkeVerplaatsingen[i].getX()][naar.getY() + mogelijkeVerplaatsingen[i].getY()];
                if (isVerplaatsingBinnenVeld(naar, mogelijkeVerplaatsingen[i]) && nieuweAfstand != 0 && (dichtstePlaats == -1 || dichtstePlaats > nieuweAfstand)) {
                    dichtstePlaats = afstanden[naar.getX() + mogelijkeVerplaatsingen[i].getX()][naar.getY() + mogelijkeVerplaatsingen[i].getY()];
                    dichtstLiggende = mogelijkeVerplaatsingen[i];
                }
            }
        }
        if (dichtstLiggende != null) {
            //System.out.println("dichtsbijzijnde plaats gevonden: " + dichtstLiggende.getX() + "  " + dichtstLiggende.getY());
            resultaat = new Coordinaat(naar.getX() + dichtstLiggende.getX(), naar.getY() + dichtstLiggende.getY());
            
            //de lijst van verdedigende ventjes opvragen
            ArrayList<Entiteit> verdediging = getEntiteiten(naar);
            //aanval uitwerken
            for (int i = 0; i < ventjes.size(); i++) {
                if (verdediging.size() > 0) {
                    Entiteit vangtSlagOp = verdediging.get(0);
                    //aanvalswaarde opzoeken
                    int aanval = ((Ventje) ventjes.get(i)).getAanvalsSterkte();
                    //overblijvende hitpoints berekenen
                    int overblijvendeHitpoints = vangtSlagOp.getHitpoints() - aanval;
                    System.out.println("na de aanval blijven nog x hitpoints over: " + overblijvendeHitpoints);
                    //kijken of het ventje dood is
                    if (overblijvendeHitpoints <= 0) {
                        System.out.println("ventje gestorven");
                        //ventje is dood, verwijderen
                        verdediging.remove(vangtSlagOp);
                        entiteiten.remove(vangtSlagOp);
                    } else {
                        //ventje heeft nog hitpoints, deze opslaan
                        vangtSlagOp.setHitpoints(overblijvendeHitpoints);
                    }
                }
            }
            for (int i = 0; i < ventjes.size(); i++) {
                //kijken of de verdedigende ventjes allemaal gestorven zijn
                if (verdediging.size() == 0) {
                    //verdediging weg, ventjes kunnen naar de plaats van de verdediging
                    ventjes.get(i).setPlaats(naar);
                    ventjes.get(i).setBeweging(null);
                } else {
                    //er blijven nog ventjes over, de aanvallende ventjes moeten naar de dichtsligende plaats
                    ventjes.get(i).setPlaats(resultaat);
                    ventjes.get(i).setBeweging(null);
                }
            }
        }
    }

    /**
     * entiteiten instellen en kijken of er ventjes aangemaakt moeten worden
     */
    public void beginBeurt(boolean aanBeurt, int spelerId, ArrayList<Entiteit> nieuweEnt) {
        spelerAanBeurt = spelerId;
        if (nieuweEnt != null) {
            entiteiten = nieuweEnt;
        }
        if (aanBeurt)
            ventjesMaken();
    }

    /**
     * Ventjes aanmaken op het speelveld
     * elk gebouw maakt ventjes
     */
    public void ventjesMaken() {
        for (int i = 0; i < entiteiten.size(); i++) {
            Entiteit item = entiteiten.get(i);
            if (item.getEigenaarId() == spelerAanBeurt && item.getClass().getPackage().getName().equalsIgnoreCase("be.hogent.iii.project.objects.gebouwen")) {
                //nieuw ventje aanmaken voor gebouw
                Random rand = new Random();
                String[] units = {"be.hogent.iii.project.objects.ventjes.Barbaar",
                    "be.hogent.iii.project.objects.ventjes.Archer",
                    "be.hogent.iii.project.objects.ventjes.Paard"
                };
                int n = rand.nextInt(30);
                System.out.println("aantal verschillende units: " + units.length + ", gekozen: " + n % units.length);
                //random kiezen tussen de verschillende klassen
                String nieuwVentjeKlasse = units[n % units.length];
                
                //sterk ventje voor debugging doeleinden
                if (n > 20) {
                    nieuwVentjeKlasse = "be.hogent.iii.project.objects.ventjes.Zealot";
                }
                
                try {
                    //0 tot 2 ventjes worden aangemaakt
                    for (int j = 0; j < n / 10; j++) {
                        Class klasse = Class.forName(nieuwVentjeKlasse);
                        System.out.println(klasse.toString() + " wordt aangemaakt");
                        Ventje nieuwVentje = (Ventje) klasse.newInstance();

                        Coordinaat plaatsNieuwVentje = ((Gebouw) item).getRallyPoint();
                        nieuwVentje.setPlaats(plaatsNieuwVentje);
                        nieuwVentje.setEigenaarId(spelerAanBeurt);
                        entiteiten.add(nieuwVentje);
                    }
                } catch (Exception ex) {
                    System.out.println("ventje aanmaken mislukt\n" + ex.getMessage());
                }
            }
        }
    }

    /**
     * alle entiteiten opvragen die zich op een bepaalde plaats bevinden
     * @param plaatsNodig de positie die men opvraagt
     * @return de gevraagde lijst van entiteiten
     */
    public ArrayList<Entiteit> getEntiteiten(Coordinaat plaatsNodig) {
        ArrayList<Entiteit> nodig = new ArrayList<Entiteit>();
        for (int i = 0; i < entiteiten.size(); i++) {
            Entiteit e = entiteiten.get(i);
            Coordinaat c = e.getPlaats();
            if (c.getX() == plaatsNodig.getX() && c.getY() == plaatsNodig.getY()) {
                nodig.add(e);
            }
        }
        return nodig;
    }

    public ArrayList<Entiteit> getEntiteiten(int x, int y) {
        return getEntiteiten(new Coordinaat(x, y));
    }

    /**
     * kijkt of een groep van ventjes verplaats kan worden naar de gewilde locatie
     * en geeft het pad dat gevolgd moet worden terug in een arraylist van coordinaten
     * @param entiteiten de ventjes die men wil verplaatsen
     * @param naar de gewilde eindbestemming
     * @return het pad dat gevolgd moet worden om de eindbestemming te bereiken,
     *      null indien het onbereikbaar is
     */
    public ArrayList<Coordinaat> actieVerplaats(ArrayList<Entiteit> entiteiten, Coordinaat naar) {
        //enkel ventjes mogen verplaatst worden
        ArrayList<Ventje> ventjes = new ArrayList<Ventje>();
        for (int i = 0; i < entiteiten.size(); i++) {
            if (entiteiten.get(i).getClass().getPackage().getName().equalsIgnoreCase("be.hogent.iii.project.objects.ventjes")) {
                ventjes.add((Ventje) entiteiten.get(i));
            }
        }
        //System.out.println("aantal ventjes: " + ventjes.size());
        
        //de maximum af te leggen afstand bepalen
        int maxAfstand = bepaalMinsteStappen(ventjes);
        boolean[][] geldigePlaatsen = getGeldigePlaatsen(ventjes);
        Coordinaat van = ventjes.get(0).getPlaats();
        int[][] afstanden = bepaalAfstandenTabel(geldigePlaatsen, van);
        boolean[][] aanvalPlaatsen = getAanvalsPlaatsen(spelerAanBeurt);

        //correctie doen als de eindbestemming een eindplaats is
        //(de afstand van afstandentabel zal 0 zijn omdat het geen geldige plaats is)
        if (afstanden[naar.getX()][naar.getY()] == 0 && aanvalPlaatsen[naar.getX()][naar.getY()]) {
            for (int k = 0; k < aantalMogelijkeVerplaatsingen; k++) {
                if (isVerplaatsingBinnenVeld(naar, mogelijkeVerplaatsingen[k])) {
                    Coordinaat buur = naar.somMet(mogelijkeVerplaatsingen[k]);
                    if ((afstanden[buur.getX()][buur.getY()] + 1 < afstanden[naar.getX()][naar.getY()] || afstanden[naar.getX()][naar.getY()] == 0) && afstanden[buur.getX()][buur.getY()] != 0) {
                        afstanden[naar.getX()][naar.getY()] = afstanden[buur.getX()][buur.getY()] + 1;
                    }
                }
            }
        }

        //kijken of de bestemming niet te ver en geldig is
        if (afstanden[naar.getX()][naar.getY()] > 0 && maxAfstand >= afstanden[naar.getX()][naar.getY()]) {

            //beweging in het ventje opslaan
            for (int i = 0; i < ventjes.size(); i++) {
                ((Ventje) ventjes.get(i)).setBeweging(naar);
            }
            //System.out.println("beweging opgeslagen: aantal stappen: " + afstanden[naar.getX()][naar.getY()]);

            //een bewegingstabel opstellen zodat de gui deze kan weergeven
            ArrayList<Coordinaat> pad = new ArrayList<Coordinaat>();

            Coordinaat vorige = new Coordinaat(naar.getX(), naar.getY());
            pad.add(vorige);
            for (int i = afstanden[naar.getX()][naar.getY()]; i > 1; i--) {
                boolean gevonden = false;
                for (int k = 0; k < aantalMogelijkeVerplaatsingen; k++) {
                    if (isVerplaatsingBinnenVeld(vorige, mogelijkeVerplaatsingen[k]) && !gevonden) {
                        Coordinaat nieuw = new Coordinaat(vorige.getX() + mogelijkeVerplaatsingen[k].getX(), vorige.getY() + mogelijkeVerplaatsingen[k].getY());
                        if (afstanden[nieuw.getX()][nieuw.getY()] == afstanden[vorige.getX()][vorige.getY()] - 1) {
                            vorige = nieuw;
                            pad.add(nieuw);
                            gevonden = true;
                        }
                    }
                }
            }

            return pad;
        } else {
            //de verplaatsing is niet mogelijk
            for (int i = 0; i < ventjes.size(); i++) {
                ((Ventje) ventjes.get(i)).setBeweging(null);
            }
            System.out.println("De afstand is te ver");
            return null;
        }
    }

    /**
     * checken of de som van de 2 coordinaten een plaats binnen het veld is
     * @param positie de positie op het veld
     * @param verplaatsing een verplaatsing
     * @return of de verplaatsing al dan niet geldig is
     */
    private boolean isVerplaatsingBinnenVeld(Coordinaat positie, Coordinaat verplaatsing) {
        if (positie.getX() + verplaatsing.getX() < 0 || positie.getX() + verplaatsing.getX() >= grootteX) {
            return false;
        } else if (positie.getY() + verplaatsing.getY() < 0 || positie.getY() + verplaatsing.getY() >= grootteY) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Bekijkt een groep ventjes en geeft de maximum afstand terug die deze groep kan afleggen
     * @param ventjes
     * @return maximum aantal stappen
     */
    private int bepaalMinsteStappen(List ventjes) {
        int min = 0;
        if (!ventjes.isEmpty()) {
            min = ((Ventje) ventjes.get(0)).getAantalStappen();
            for (int i = 1; i < ventjes.size(); i++) {
                if (min > ((Ventje) ventjes.get(i)).getAantalStappen()) {
                    min = ((Ventje) ventjes.get(i)).getAantalStappen();
                }
            }
        }
        System.out.println("min stappen = " + min);
        return min;
    }

    /**
     * Gaat kijken welke plaatsen van het veld aanvalsplaatsen zijn
     * @param spelersId
     * @return
     */
    private boolean[][] getAanvalsPlaatsen(int spelersId) {
        boolean[][] plaatsen = new boolean[grootteX][grootteY];
        for (int i = 0; i < grootteX; i++) {
            for (int j = 0; j < grootteY; j++) {
                plaatsen[i][j] = false;
            }
        }
        for (int i = 0; i < entiteiten.size(); i++) {
            Entiteit item = entiteiten.get(i);
            //System.out.println("object checken: " + item.getClass().getName());
            if (item.getEigenaarId() != spelersId) {
                plaatsen[item.getPlaats().getX()][item.getPlaats().getY()] = true;
            }
        }
        return plaatsen;
    }

    /**
     * kijkt welke vakjes geldige plaatsen zijn om ventjes op te zetten van de speler
     * beter ineens alle vakjes opvragen omdat voor 1 vakje te controleren ook 
     * heel de lijst van objecten moet afgegaan worden.
     */
    private boolean[][] getGeldigePlaatsen(ArrayList<Ventje> ventjes) {
        BewegingsControleur controle = new BewegingsControleur();
        boolean[][] plaatsen = new boolean[grootteX][grootteY];
        for (int i = 0; i < grootteX; i++) {
            for (int j = 0; j < grootteY; j++) {
                //controle op basis van ventje en soort ondergrond
                if (controle.isGeldigeBeweging(null, speelveld[i][j])) {
                    plaatsen[i][j] = true;
                } else {
                    plaatsen[i][j] = false;
                }
            }
        }
        for (int i = 0; i < entiteiten.size(); i++) {
            Entiteit item = entiteiten.get(i);
            //System.out.println("object checken: " + item.getClass().getName());
            if (item.getEigenaarId() != ventjes.get(0).getEigenaarId()) {
                plaatsen[item.getPlaats().getX()][item.getPlaats().getY()] = false;
            } else if (item.getClass().getPackage().getName().equalsIgnoreCase("be.hogent.iii.project.objects.gebouwen")) {
                plaatsen[item.getPlaats().getX()][item.getPlaats().getY()] = false;
            }
        }
        /* geldigePlaatsenTabel uitschrijven
        for (int i = 0; i < grootteY; i++) {
        for (int j = 0; j < grootteX; j++) {
        System.out.print(" " + plaatsen[j][i]);
        }
        System.out.println();
        }
         */
        return plaatsen;
    }

    private int[][] bepaalAfstandenTabel(boolean[][] geldigeplaatsen, Coordinaat van) {
        int[][] afstand = new int[grootteX][grootteY];
        afstand[van.getX()][van.getY()] = 1;
        boolean verder = true;
        int afstandBereken = 1;
        while (verder) {
            verder = false;

            //alle vakjes afgaan en kijken welke op afstand afstandBereken liggen
            //en bij buren afstand 1 hoger maken als afstandBereken
            for (int i = 0; i < grootteX; i++) {
                for (int j = 0; j < grootteY; j++) {
                    if (afstand[i][j] == afstandBereken) {
                        for (int k = 0; k < aantalMogelijkeVerplaatsingen; k++) {
                            int nieuwX = i + mogelijkeVerplaatsingen[k].getX();
                            int nieuwY = j + mogelijkeVerplaatsingen[k].getY();
                            if (isVerplaatsingBinnenVeld(new Coordinaat(i, j), mogelijkeVerplaatsingen[k]) && (afstand[nieuwX][nieuwY] > afstand[i][j] || afstand[nieuwX][nieuwY] == 0) && geldigeplaatsen[nieuwX][nieuwY]) {
                                afstand[nieuwX][nieuwY] = afstandBereken + 1;
                                verder = true;
                            }
                        }
                    }
                }
            }
            afstandBereken++;
        }

        //overal 1 aftellen zodat het de werkelijke afstanden zijn
        for (int i = 0; i < grootteY; i++) {
            for (int j = 0; j < grootteX; j++) {
                if (afstand[j][i] > 0) {
                    afstand[j][i]--;
                }
            //System.out.print(" " + afstand[j][i]);
            }
        //System.out.println();
        }
        //bij de startplaats 1 bijtellen zodat dit een geldige plaats is (0 = ongeldig)
        afstand[van.getX()][van.getY()]++;
        
        return afstand;
    }
}

